/*
 * snmp_var_route.c - return a pointer to the named variable.
 *
 *
 */
/***********************************************************
	Copyright 1988, 1989 by Carnegie Mellon University
	Copyright 1989	TGV, Incorporated

		      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of CMU and TGV not be used
in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.

CMU AND TGV DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL CMU OR TGV BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
******************************************************************/

#include "config.h"
#define GATEWAY			/* MultiNet is always configured this way! */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <syslog.h>
#include <machine/param.h>  /* req'd by mbuf.h on FreeBSD, BSDI, ... */
#include <sys/mbuf.h>
#include <net/if.h>
#define KERNEL		/* to get routehash, RTHASHSIZ, rt_tables */
#include <net/route.h>
#undef	KERNEL
/*#define rt_unit rt_hash		       /* Reuse this field for device # */
#include <nlist.h>
#ifndef NULL
#define NULL 0
#endif
#ifdef BSD44
#define write_rte NULL
#endif

#define CACHE_TIME (120)	    /* Seconds */

#include "asn1.h"
#include "snmp.h"
#include "snmp_impl.h"
#include "mib.h"
#include "snmp_vars.h"
#include "routes.h"

#ifndef  MIN
#define  MIN(a,b)                     (((a) < (b)) ? (a) : (b))
#endif

static struct rtetab **rthead=0;
static int rtsize=0, rtalloced=0;

#define  KNLookup(nl_which, buf, s)   (klookup( nl[nl_which].n_value, buf, s))

static struct nlist nl[] = {
#define N_RTHOST       0
	{ "_rthost" },
#define N_RTNET        1
	{ "_rtnet" },
#define N_RTHASHSIZE   2
	{ "_rthashsize" },
#define N_RTHEAD       3
        { "_radix_node_head"},  
#define N_RTREE        4
        { "_rt_tables"},
	0,
};

#ifndef write_rte
extern write_rte();
#endif
static void Route_Scan_Reload();

u_char *
var_ipRouteEntry(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output strings */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    /*
     * object identifier is of form:
     * 1.3.6.1.2.1.4.21.1.1.A.B.C.D,  where A.B.C.D is IP address.
     * IPADDR starts at offset 10.
     */
    register int Save_Valid, result, RtIndex;
    static int saveNameLen=0, saveExact=0, saveRtIndex=0;
    static oid saveName[14], Current[14];
    u_char *cp;
    oid *op;


    /*
     *	OPTIMIZATION:
     *
     *	If the name was the same as the last name, with the possible
     *	exception of the [9]th token, then don't read the routing table
     *
     */

    if ((saveNameLen == *length) && (saveExact == exact)) {
	register int temp=name[9];
	name[9] = 0;
	Save_Valid = (compare(name, *length, saveName, saveNameLen) == 0);
	name[9] = temp;
    } else Save_Valid = 0;

    if (Save_Valid) {
	register int temp=name[9];    /* Fix up 'lowest' found entry */
	bcopy((char *) Current, (char *) name, 14 * sizeof(oid));
	name[9] = temp;
	*length = 14;
	RtIndex = saveRtIndex;
    } else {
	/* fill in object part of name for current (less sizeof instance part) */

	bcopy((char *)vp->name, (char *)Current, (int)(vp->namelen) * sizeof(oid));

#if 0
	/*
	 *  Only reload if this is the start of a wildcard
	 */
	if (*length < 14) {
	    Route_Scan_Reload();
	}
#else
        Route_Scan_Reload();
#endif
	for(RtIndex=0; RtIndex < rtsize; RtIndex++) {
	    cp = (u_char *)&rthead[RtIndex]->rt_dst;
	    op = Current + 10;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;

	    result = compare(name, *length, Current, 14);
	    if ((exact && (result == 0)) || (!exact && (result < 0)))
		break;
	}
	if (RtIndex >= rtsize) return(NULL);
	/*
	 *  Save in the 'cache'
	 */
	bcopy((char *) name, (char *) saveName, *length * sizeof(oid));
	saveName[9] = '\0';
	saveNameLen = *length;
	saveExact = exact;
	saveRtIndex = RtIndex;
	/*
	 *  Return the name
	 */
	bcopy((char *) Current, (char *) name, 14 * sizeof(oid));
	*length = 14;
    }

    *write_method = write_rte;
    *var_len = sizeof(long_return);



    switch(vp->magic){
	case IPROUTEDEST:
	    return(u_char *) &rthead[RtIndex]->rt_dst;
	case IPROUTEIFINDEX:
	    long_return = (u_long)rthead[RtIndex]->rt_unit;
	    return (u_char *)&long_return;
	case IPROUTEMETRIC1:
	    long_return = (rthead[RtIndex]->rt_rt.rt_flags & RTF_GATEWAY) ? 1 : 0;
	    return (u_char *)&long_return;
	case IPROUTEMETRIC2:
	case IPROUTEMETRIC3:
	case IPROUTEMETRIC4:
	case IPROUTEMETRIC5:
	    long_return = -1;
	    return (u_char *)&long_return;
	case IPROUTENEXTHOP:
	    return(u_char *) &rthead[RtIndex]->rt_gateway;
	case IPROUTETYPE:
	    long_return = (rthead[RtIndex]->rt_rt.rt_flags & RTF_GATEWAY) ? 4 : 3;
	    return (u_char *)&long_return;
	case IPROUTEPROTO:
	    long_return = (rthead[RtIndex]->rt_rt.rt_flags & RTF_DYNAMIC) ? 4 : 2;
	    return (u_char *)&long_return;
	case IPROUTEAGE:
	    long_return = 0;
	    return (u_char *)&long_return;
	case IPROUTEMASK:
	    return(u_char *) &rthead[RtIndex]->rt_mask;
	case IPROUTEINFO:
            {
               static oid nulloid[] = {0, 0};
               *var_len = sizeof(nulloid);
               return (u_char *)nulloid;
            }
	default:
	    ERROR("");
   }
   return NULL;
}

cmu_init_routes(){
    nlist(VMUNIX,nl);
}

/*
 *	Create a host table
 */
static int qsort_compare(r1,r2)
struct rtetab **r1, **r2;
{
	register u_long dst1 = ntohl((*r1)->rt_dst);
	register u_long dst2 = ntohl((*r2)->rt_dst);

	/*
	 *	Do the comparison
	 */
	if (dst1 == dst2) return(0);
	if (dst1 > dst2) return(1);
	return(-1);
}

static int  
add_route(re)
   register struct rtentry *re;
{
   register struct rtetab *rt;
#ifdef BSD44
   struct sockaddr rtsock;
#endif

   /*
    *   Allocate a block to hold it and add it to the database
    */
   if (rtsize >= rtalloced) {
      rthead = (struct rtetab **)realloc((char *)rthead, 
       2 * rtalloced * sizeof(struct rtetab *));
      bzero((char *) &rthead[rtalloced], rtalloced * sizeof(struct rtetab *));
      rtalloced *= 2;
   }
   if (!rthead[rtsize])
      rthead[rtsize] = (struct rtetab *) malloc(sizeof(struct rtetab));

   /*
    *   Add this to the database
    */
   rt = rthead[rtsize];
   bcopy((char *)re, (char *)&rt->rt_rt, sizeof(struct rtentry));
   rtsize++;

#ifndef BSD44
   rt->rt_dst     = ((struct sockaddr_in *)&re->rt_dst)->sin_addr.s_addr;
   rt->rt_gateway = ((struct sockaddr_in *)&re->rt_gateway)->sin_addr.s_addr;
   if (rt->rt_rt.rt_flags & RTF_HOST)
      rt->rt_mask = (u_long) 0xffffffff;
   else if (rt->rt_dst != 0L) {
      if (!(rt->rt_dst & IN_CLASSA_HOST))
         rt->rt_mask = IN_CLASSA_NET;
      else if (!(rt->rt_dst & IN_CLASSB_HOST))
         rt->rt_mask = IN_CLASSB_NET;
      else
         rt->rt_mask = IN_CLASSC_NET;
   } else
      rt->rt_mask = 0L;
#else
   klookup(rt_key(re), (char *)&rtsock, sizeof(rtsock));
   rt->rt_dst     = ((struct sockaddr_in *)&rtsock)->sin_addr.s_addr;
   if (rt_mask(re)) {
      klookup(rt_mask(re), (char *)&rtsock, sizeof(rtsock));
      rt->rt_mask    = ((struct sockaddr_in *)&rtsock)->sin_addr.s_addr;
   } else
      rt->rt_mask    = (u_long)0xffffffff;
   klookup(re->rt_gateway, (char *)&rtsock, sizeof(rtsock));
   rt->rt_gateway = ((struct sockaddr_in *)&rtsock)->sin_addr.s_addr;
#endif
   return 1;
}

#if defined(mips)

static void
Route_Scan_Reload()
{
	struct rtentry **routehash, mb;
	register struct rtentry *m;
	struct ifnet ifnet;
	struct rtentry *rt;
	int i, table;
	register char *cp;
	char name[16], temp[16];
	static int Time_Of_Last_Reload=0;
	struct timeval now;
	int hashsize;
	extern char *index(), *malloc();

	gettimeofday(&now, (struct timezone *)0);
	if (Time_Of_Last_Reload+CACHE_TIME > now.tv_sec)
	    return;
	Time_Of_Last_Reload =  now.tv_sec;

	/*
	 *  Makes sure we have SOME space allocated for new routing entries
	 */
	if (!rthead) {
	    rthead = (struct rtetab **) malloc(100 * sizeof(struct rtetab *));
	    if (!rthead) {
		ERROR("malloc");
		return;
	    }
	    bzero((char *)rthead, 100 * sizeof(struct rtetab *));
	    rtalloced = 100;
	}

	for (table=N_RTHOST; table<=N_RTNET; table++) {

	    KNLookup(N_RTHASHSIZE, (char *)&hashsize, sizeof(hashsize));
	    routehash = (struct rtentry **)malloc(hashsize * sizeof(struct mbuf *));
	    KNLookup( table, (char *)routehash, hashsize * sizeof(struct mbuf *));
	    for (i = 0; i < hashsize; i++) {
		if (routehash[i] == 0)
			continue;
		m = routehash[i];
		while (m) {
		    /*
		     *	Dig the route out of the kernel...
		     */
		    klookup(m , (char *)&mb, sizeof (mb));
		    m = mb.rt_next;

		    rt = &mb;
		    if (rt->rt_ifp != 0) {
			klookup( rt->rt_ifp, (char *)&ifnet, sizeof (ifnet));
			klookup( ifnet.if_name, name, 16);
			name[15] = '\0';
			cp = (char *) index(name, '\0');
			*cp++ = ifnet.if_unit + '0';
			*cp = '\0';
/*			if (strcmp(name,"lo0") == 0) continue; */


			Interface_Scan_Init();
			while (Interface_Scan_Next((int *)&rt->rt_unit, temp, 0, 0) != 0) {
			    if (strcmp(name, temp) == 0) break;
			}
		    }

         add_route(rt);
		}
	    }
	}
	/*
	 *  Sort it!
	 */
	qsort((char *)rthead,rtsize,sizeof(rthead[0]),qsort_compare);
}

#else


#ifdef BSD44
static int  
get_radix_node (rn)
   struct radix_node *rn;
{
   struct radix_node rnode;
   struct rtentry rtentry;

   for (;;) {
      klookup(rn, (char *)&rnode, sizeof(rnode));
      if (rnode.rn_b < 0) {
         if (!(rnode.rn_flags & RNF_ROOT)) {
            klookup(rn, (char *) &rtentry, sizeof(rtentry));
            if (!add_route(&rtentry))
               return 0;
         }
         if (rn = rnode.rn_dupedkey)
            continue;
      } else {
         if (!get_radix_node (rnode.rn_l) || !get_radix_node (rnode.rn_r))
            return 0;
      }
      return 1;
   }
}

static int   /* returns: true on success, false on failure */
get_radix_nodes () 
{  
   struct radix_node_head *rnh, head;
   int i;

#ifdef RT_TABLES
   KNLookup( N_RTREE, (char *)rt_tables, sizeof(rt_tables));
   for (i = 0; i <= AF_MAX; i++) {
      if ((rnh = rt_tables[i]) == 0)
         continue;
      klookup(rnh, (char *)&head, sizeof(head));
      if (i == AF_UNSPEC) /* XXX */
         continue;
      if (!get_radix_node (head.rnh_treetop))
         return 0;
   }
#else
   KNLookup( N_RTHEAD, (char *)&rnh, sizeof(rnh));
   while (rnh) { 
      klookup(rnh, (char *)&head, sizeof(head));
      rnh = head.rnh_next;
      if (head.rnh_af == AF_UNSPEC)
         continue;
      if (!get_radix_node (head.rnh_treetop))
         return 0;
   }   
#endif
   return 1;
}
#endif

static void
Route_Scan_Reload()
{
	struct mbuf **routehash, mb;
	register struct mbuf *m;
	struct ifnet ifnet;
	struct rtentry *rt;
	int i, table;
	register char *cp;
	char name[16], temp[16];
	static int Time_Of_Last_Reload=0;
	struct timeval now;
	int hashsize;

	gettimeofday(&now, (struct timezone *)0);
	if (Time_Of_Last_Reload+CACHE_TIME > now.tv_sec)
	  return;
	Time_Of_Last_Reload =  now.tv_sec;
	
	/*
	 *  Makes sure we have SOME space allocated for new routing entries
	 */
	if (!rthead) {
	    rthead = (struct rtetab **) malloc(100 * sizeof(struct rtetab *));
	    if (!rthead) {
		ERROR("malloc");
		return;
	    }
	    bzero((char *)rthead, 100 * sizeof(struct rtetab *));
	    rtalloced = 100;
	}

#ifdef BSD44
   if (!get_radix_nodes()) {
      rtsize = 0;
      return;
   }
#else
	for (table=N_RTHOST; table<=N_RTNET; table++) {
#ifdef sunV3
	    hashsize = RTHASHSIZ;
#else
	    KNLookup( N_RTHASHSIZE, (char *)&hashsize, sizeof(hashsize));
#endif
	    routehash = (struct mbuf **)malloc(hashsize * sizeof(struct mbuf *));
	    KNLookup( table, (char *)routehash, hashsize * sizeof(struct mbuf *));
	    for (i = 0; i < hashsize; i++) {
          if (routehash[i] == 0)
             continue;
          m = routehash[i];
          while (m) {
   		    /*
   		     *	Dig the route out of the kernel...
   		     */
   		    klookup( m , (char *)&mb, sizeof (mb));
   		    m = mb.m_next;
   		    rt = mtod(&mb, struct rtentry *);

   		    if (rt->rt_ifp != 0) {
                klookup(rt->rt_ifp, (char *)&ifnet, sizeof (ifnet));
                klookup(ifnet.if_name, name, 16);
                name[15] = '\0';
                cp = (char *) index(name, '\0');
                *cp++ = ifnet.if_unit + '0';
                *cp = '\0';
                if (strcmp(name,"lo0") == 0) 
                   continue; 
                add_route(rt);
                Interface_Scan_Init();
                while (Interface_Scan_Next((int *)&rthead[rtsize-1]->rt_unit, 
                 temp, 0, 0)) {
   			       if (!strcmp(name, temp))
                      break;
   			    }
   		    }
   	    }
 	   }
   }
#endif
	/*
	 *  Sort it!
	 */
	qsort((char *)rthead,rtsize,sizeof(rthead[0]),qsort_compare);
}
#endif
